﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
using System.Text.RegularExpressions;

using King.Extensions;
using King.Text;
using King.Language.Grammar.FsYacc;

namespace csg {

    public class GrammarInfo {
        public static string ToName(string name) {
            name = name.Trim();
            if (!char.IsLetter(name[0]))
                return name;
            return name.StringSplit("-").Select(o => o.ToUpperFirst()).StringJoin().ToLowerFirst();
        }

        private string m_file;
        private Dictionary<string, ProductionInfo> m_productionByName;
        private Dictionary<string, TerminalInfo> m_terminalsByName;
        private List<TerminalInfo> m_terminals;
        private HashSet<string> m_productionsToSkip;

        public GrammarInfo(string file, HashSet<string> productionsToSkip = null) {
            m_file = file;
            m_productionsToSkip = productionsToSkip ?? new HashSet<string>();
            m_terminalsByName = new Dictionary<string, TerminalInfo>();

            // load everything
            Productions();
            Productions().SelectMany(o => o.Expansions().SelectMany(x => x.Symbols())).ToArray();
        }

        private bool IsHeader(string line) {
            return line.ToLower().StartsWith("b.");
        }
        private bool IsProduction(string line) {
            return (line.EndsWith(":") || line.EndsWith("one of")) && !line.EndsWith(" :");
        }
        private void AddExpansion(ProductionInfo production, Queue<string> lines, bool oneOf) {
            var expansions = new List<ExpansionInfo>();
            
            while (lines.Any() && !IsProduction(lines.Peek())) {
                var expansion = lines.Dequeue();
                if (IsHeader(expansion))
                    continue;
                if (lines.Any() && lines.Peek().StartsWith("\t\t"))
                    expansion += lines.Dequeue();

                if (oneOf) {
                    foreach (var o in expansion.StringSplit(' ', '\t'))
                        production.AddExpansion(new ExpansionInfo(production, o));

                } else {
                    production.AddExpansion(new ExpansionInfo(production, expansion));
                }
            }
        }

        internal TerminalInfo GetOrAddTerminal(string value) {
            var name = ToName(value);
            var result = m_terminalsByName.GetValueOrDefault(name);
            if (result == null)
                m_terminalsByName[name] = result = new TerminalInfo(this, name, m_productionsToSkip.Contains(name));
            return result;
        }
        
        private HashSet<TerminalInfo> m_optional;

        public IEnumerable<TerminalInfo> Terminals() {
            return m_terminalsByName.Values;
        }
        public ProductionInfo GetProduction(string name) {
            if (m_productionByName == null)
                Productions();
            return m_productionByName.GetValueOrDefault(name);
        }
        public IEnumerable<ProductionInfo> Productions() {
            if (m_productionByName == null) {
                m_productionByName = new Dictionary<string, ProductionInfo>();

                var nl = Environment.NewLine;
                var file = File.ReadAllText(m_file);
                file = file.Replace("right-shift", ">>");
                file = file.Replace(">>-assignment", ">>=");
                file = file.Replace(((char)8211).ToString(), "-");
                file = file.Replace("Carriage return character (U+000D)", "'\\u000d'");
                file = file.Replace("Horizontal tab character (U+0009)", "'\\u0009'");
                file = file.Replace("Vertical tab character (U+000B)", "'\\u000b'");
                file = file.Replace("Form feed character (U+000C)", "'\\u000c'");
                file = file.Replace("line feed character (U+000A)", "'\\u000a'");
                file = file.Replace("Line feed character (U+000A)", "'\\u000a'");
                file = file.Replace("Paragraph separator character (U+2029)", "'\\u2029'");
                file = file.Replace("Next line character (U+0085)", "'\\u2085'");
                file = file.Replace("Line separator character (U+2028)", "'\\u2028'");
                file = file.Replace(" followed by", "");
                file = file.Replace(" (the underscore character U+005F)", "");
                file = file.Replace("Any Unicode character except a new-line-character", "{{ not new-line-character }}");
                file = file.Replace("Any Unicode character except / or *", "{{ not / * }}");
                file = file.Replace("Any character with Unicode class Zs", "/Zs");
                file = file.Replace("A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl", "/Lu /Ll /Lt /Lm /Lo /Ln");
                file = file.Replace("A Unicode character of classes Mn or Mc", "/Mn /Mc");
                file = file.Replace("A Unicode character of the class Nd", "/Nd");
                file = file.Replace("A Unicode character of the class Pc", "/Pc");
                file = file.Replace("A Unicode character of the class Cf", "/Cf");
                file = file.Replace("A unicode-escape-sequence representing a character of classes Lu, Ll, Lt, Lm, Lo, or Nl" + Environment.NewLine, "");
                file = file.Replace("A unicode-escape-sequence representing a character of classes Mn or Mc" + Environment.NewLine, "");
                file = file.Replace("A unicode-escape-sequence representing a character of the class Nd" + Environment.NewLine, "");
                file = file.Replace("A unicode-escape-sequence representing a character of the class Pc" + Environment.NewLine, "");
                file = file.Replace("A unicode-escape-sequence representing a character of the class Cf" + Environment.NewLine, "");
                file = file.Replace(@" Any character except ' (U+0027), \ (U+005C), and new-line-character", @"{{ character not ' \ new-line-character }}");
                file = file.Replace(@"Any character except "" (U+0022), \ (U+005C), and new-line-character", @"{{ character not "" \ new-line-character }}");
                file = file.Replace(@"any character except """, @"{{ character not "" }}");
                file = file.Replace(@"Any identifier-or-keyword except true or false", @"{{ identifier-or-keyword not true false }}");
                file = file.Replace(@"Any input-character except #", @"{{ input-character not # }}");
                file = file.Replace(@"Any input-character except """, @"{{ input-character not "" }}");
                file = file.Replace(
@"token:
identifier
keyword
integer-literal
real-literal
character-literal
string-literal
operator-or-punctuator", "");

                file = file.Replace(
@"input-element:
whitespace
comment
token",

@"input-element:
whitespace
comment
identifier
operator-or-punctuator");

                // remove keyword
                file = Regex.Replace(file, "keyword:[^:]*while", "", RegexOptions.Singleline);
                file = Regex.Replace(file, nl + "keyword" + nl, nl);
                file = file.Replace("An identifier-or-keyword that is not a keyword", "identifier-or-keyword");

                file = file.Replace(@"Any input-character except """, @"{{ input-character not "" }}");

                file = file.Replace(@"+   -   !   ~   ++   --   true   false",
                    "+   -   !   ~   ++   --   boolean");
                file = file.Replace(@"input-element:", @"input-element:" + Environment.NewLine + "literal");

                file = file.Replace(
@"primary-no-array-creation-expression:
literal",

@"primary-no-array-creation-expression:
boolean
integer
real
character
string
null");

                var lines = file.StringSplit(Environment.NewLine).Select(o => o.Trim(' ')).ToQueue();

                while (lines.Any()) {
                    if (IsHeader(lines.Peek())) {
                        lines.Dequeue();
                        continue;
                    }

                    var line = lines.Dequeue();
                    if (!IsProduction(line))
                        throw new ArgumentException();

                    var oneOf = line.EndsWith("one of");
                    if (oneOf)
                        line = line.ReplaceEnd("one of").TrimEnd();

                    var production = new ProductionInfo(this, line);
                    if (m_productionByName.ContainsKey(production.Name)) {

                        if (lines.Peek() != "...") {
                            AddExpansion(production, lines, false);
                            continue;
                        }

                        var elipses = lines.Dequeue();
                        production = m_productionByName[production.Name];
                    }
                    AddExpansion(production, lines, oneOf);

                    if (m_productionsToSkip.Contains(production.Name))
                        continue;

                    m_productionByName[production.Name] = production;
                }
            }
            return m_productionByName.Values;
        }

        internal bool IsOptional(TerminalInfo terminalInfo) {
            if (m_optional == null)
                m_optional =
                   (from o in Productions()
                    from e in o.Expansions()
                    from s in e.Symbols()
                    where s.IsTerminal && s.IsOptional
                    select s.SymbolAsTerminal).ToHashSet();
            return m_optional.Contains(terminalInfo);
        }
    }

    public enum TerminalType {
        PunctuatorOperator,
        Keyword,
        Regex,
    }
    public class TerminalInfo {

        //x.y  f(x)  a[x]  x++  x--  new typeof  default  checked  unchecked  delegate
        //+  -  !  ~  ++x  --x  (T)x
        //*  /  %
        //+  -
        //<<  >>
        //<  >  <=  >=  is  as
        //==  !=
        //&
        //^
        //|
        //&&
        //||
        //??
        //?:
        //=  *=  /=  %=  +=  -=  <<=  >>=  &=  ^=  |= =>

        private static Dictionary<string, string> m_terminalNames;
        static TerminalInfo() {
            var terminals = @"- Dash
            -- DoubleDash
            "" Quote
            # Pound
            ! Not
            != NotEqual
            % Percent
            %= PrecentEqual
            & Amperstand
            && DoubleAmperstand
            &= AmperstandEqual
            ( OpenParentheses
            ) CloseParentheses
            * Astrix
            *= AstrixEqual
            , Comma
            . Period
            / ForwardSlash
            /= ForwardSlashEqual
            : Colon
            :: DoubleColon
            ; SemiColon
            ? QuestionMark
            ?? DoubleQuestionMark
            [ OpenSquareBracket
            ] CloseSquareBracket
            ^ Carrot
            ^= CarrotEqual
            { OpenCurlyBracket
            | Bar
            || DoubleBar
            |= BarEqual
            } CloseCurlyBracket
            ~ Tilda
            + Plus
            ++ DoublePlus
            += PlusEqual
            < LessThan
            << DoubleLessThan
            <<= DoubleLessThanEqual
            <= LessThanEqual
            = Equal
            -= DashEqual
            == DoubleEqual
            => EqualGreaterThan
            > GreaterThan
            -> DashGreaterThan
            >= GreaterThanEqual
            >> DoubleGreaterThan
            >>= DoubleGreaterThanEqual";

            m_terminalNames = new Dictionary<string, string>();
            var enumerator = terminals.StringSplit().Select(o => o.Trim()).GetEnumerator();
            while (enumerator.MoveNext()) {
                var terminal = enumerator.Current;
                enumerator.MoveNext();
                var name = enumerator.Current;
                m_terminalNames[terminal] = name;
            }
        }

        private GrammarInfo m_grammar;
        private string m_value;
        private string m_name;
        private bool m_isRegex;

        internal TerminalInfo(GrammarInfo grammar, string value, bool isRegex = false) {
            m_grammar = grammar;
            m_value = value;
            m_isRegex = isRegex;
            m_name = m_terminalNames.GetValueOrDefault(value) ?? value;
            if (m_name == m_value)
                m_value = null;
        }

        public GrammarInfo Grammar {
            get { return m_grammar; }
        }
        public bool IsOptional {
            get {
                return m_grammar.IsOptional(this); 
            }
        }
        public bool IsRegex {
            get { return m_isRegex; }
        }
        public bool IsKeyword {
            get { return Type == TerminalType.Keyword; }
        }
        public bool IsPunctuator {
            get { return Type == TerminalType.PunctuatorOperator; }
        }
        public TerminalType Type {
            get {
                if (IsRegex)
                    return TerminalType.Regex;
                if (Symbol != null)
                    return TerminalType.PunctuatorOperator;
                return TerminalType.Keyword;
            }
        }
        public string Punctuator {
            get {
                if (Symbol.Length == 1)
                    return "'" + Symbol.Replace("'", "\\'") + "'";
                return "\"" + Symbol.Replace("\"", "\\\"") + "\""; 
            }
        }
        public string Symbol {
            get { return m_value; }
        }
        public string Name {
            get { return m_name; }
        }
        public string Text {
            get { return Symbol ?? Name; }
        }

        public override string ToString() {
            return Text;
        }
    }
    public class ProductionInfo {
        private string m_name;
        private GrammarInfo m_grammar;
        private List<ExpansionInfo> m_expansions;
        private bool? m_isEnumeration;
        private ProductionInfo m_enumerationOf;

        public ProductionInfo(GrammarInfo grammar, string line) {
            m_grammar = grammar;
            m_expansions = new List<ExpansionInfo>();

            var name = line.TrimEnd(':');
            m_name = GrammarInfo.ToName(name);
        }

        internal void AddExpansion(ExpansionInfo expansionInfo) {
            m_expansions.Add(expansionInfo);
        }

        private bool IsEnumerationHelper() {
            var expansions = Expansions().ToArray();
            if (expansions.Length != 2)
                return false;
            if (expansions[0].Symbols().Count() != 1)
                return false;
            if (expansions[0].Symbols().First().Name + "s" != Name)
                return false;

            if (expansions[1].Symbols().Count() != 2)
                return false;
            if (expansions[1].Symbols().First().Name != Name)
                return false;
            if (expansions[1].Symbols().Skip(1).First().Name + "s" != Name)
                return false;

            return true;
        }

        public bool IsEnumeration {
            get {
                if (m_isEnumeration == null)
                    m_isEnumeration = IsEnumerationHelper();
                return (bool)m_isEnumeration;
            }
        }
        public ProductionInfo EnumerationOf {
            get {
                if (m_enumerationOf == null) {
                    if (IsEnumeration)
                        m_enumerationOf = Expansions().First().Symbols().First().SymbolAsNonTerminal;
                }
                return m_enumerationOf;
            }
        }
        public string Name {
            get { return m_name; }
        }
        public GrammarInfo Grammar {
            get { return m_grammar; }
        }
        public IEnumerable<ExpansionInfo> Expansions() {
            return m_expansions;
        }

        public override string ToString() {
            return m_name;
        }
    }
    public class ExpansionInfo {
        private ProductionInfo m_production;
        private string m_line;
        private int m_id;
        private IEnumerable<SymbolInfo> m_expansion;

        public ExpansionInfo(ProductionInfo production, string line) {
            var last = production.Expansions().LastOrDefault();
            if (last == null)
                m_id = 0;
            else
                m_id = last.m_id + 1;

            m_production = production;
            m_line = line;
        }

        public int Id {
            get { return m_id; }
        }
        public ProductionInfo Production {
            get { return m_production; }
        }
        public GrammarInfo Grammar {
            get { return Production.Grammar; }
        }
        public IEnumerable<SymbolInfo> Symbols() {
            if (m_expansion == null) {
                int id = 0;
                m_expansion =
                   (from o in m_line.StringSplit(' ', '\t')
                    let isOptional = o.EndsWith("opt")
                    let rawName = o.ReplaceEnd("opt")
                    let name = GrammarInfo.ToName(rawName)
                    let symbol = (object)Grammar.GetProduction(name) ??
                        Grammar.GetOrAddTerminal(rawName)
                    select new SymbolInfo(this, symbol, isOptional, id++))
                    .ToArray();
            }
            return m_expansion;
        }

        public override string ToString() {
            return m_line;
        }
    }
    public class SymbolInfo {
        private static Dictionary<Tuple<string, int, int>, string> s_symbolNames;

        static SymbolInfo() {
            s_symbolNames = new[] {
                new { Production = "InterfaceAccessors", Expansion = 2, Symbol = 0, Name = "getAttribute" },
                new { Production = "InterfaceAccessors", Expansion = 2, Symbol = 3, Name = "setAttribute" },
                new { Production = "InterfaceAccessors", Expansion = 3, Symbol = 0, Name = "getAttribute" },
                new { Production = "InterfaceAccessors", Expansion = 3, Symbol = 3, Name = "setAttribute" },
                
                new { Production = "IfStatement", Expansion = 1, Symbol = 4, Name = "ifEmbeddedStatement" },
                new { Production = "IfStatement", Expansion = 1, Symbol = 6, Name = "elseEmbeddedStatement" },

                new { Production = "ConditionalExpression", Expansion = 1, Symbol = 2, Name = "ifExpression" },
                new { Production = "ConditionalExpression", Expansion = 1, Symbol = 4, Name = "elseExpression" },

                new { Production = "GroupClause", Expansion = 0, Symbol = 1, Name = "groupExpression" },
                new { Production = "GroupClause", Expansion = 0, Symbol = 3, Name = "byExpression" },

                new { Production = "JoinClause", Expansion = 0, Symbol = 4, Name = "inExpression" },
                new { Production = "JoinClause", Expansion = 0, Symbol = 6, Name = "onExpression" },
                new { Production = "JoinClause", Expansion = 0, Symbol = 8, Name = "equalsExpression" },

                new { Production = "JoinIntoClause", Expansion = 0, Symbol = 4, Name = "inExpression" },
                new { Production = "JoinIntoClause", Expansion = 0, Symbol = 6, Name = "onExpression" },
                new { Production = "JoinIntoClause", Expansion = 0, Symbol = 8, Name = "equalsExpression" },

                new { Production = "BinaryOperatorDeclarator", Expansion = 0, Symbol = 0, Name = "returnType" },
                new { Production = "BinaryOperatorDeclarator", Expansion = 0, Symbol = 4, Name = "lhsType" },
                new { Production = "BinaryOperatorDeclarator", Expansion = 0, Symbol = 7, Name = "rhsType" },

                new { Production = "ConversionOperatorDeclarator", Expansion = 0, Symbol = 2, Name = "toType" },
                new { Production = "ConversionOperatorDeclarator", Expansion = 0, Symbol = 4, Name = "fromType" },
                new { Production = "ConversionOperatorDeclarator", Expansion = 1, Symbol = 2, Name = "toType" },
                new { Production = "ConversionOperatorDeclarator", Expansion = 1, Symbol = 4, Name = "fromType" },

                new { Production = "UnaryOperatorDeclarator", Expansion = 0, Symbol = 0, Name = "returnType" },
                new { Production = "UnaryOperatorDeclarator", Expansion = 0, Symbol = 4, Name = "type" },

            }.ToDictionary(o => new Tuple<string, int, int>(
                o.Production.ToLowerFirst(), o.Expansion, o.Symbol), o => o.Name);
        }

        private ExpansionInfo m_expansion;
        private object m_symbol;
        private bool m_isOptional;
        private string m_name;
        private int m_id;

        internal SymbolInfo(ExpansionInfo expansion, object symbol, bool isOptional, int id) {
            m_expansion = expansion;
            m_symbol = symbol;
            m_isOptional = isOptional;
            m_id = id;
        }

        public ProductionInfo Production {
            get { return Expansion.Production; }
        }
        public ExpansionInfo Expansion {
            get { return m_expansion; }
        }
        public bool IsTerminal {
            get { return m_symbol is TerminalInfo; }
        }
        public bool IsNonTerminal {
            get { return m_symbol is ProductionInfo; }
        }
        public object Symbol {
            get { return m_symbol; }
        }
        public ProductionInfo SymbolAsNonTerminal {
            get { return m_symbol as ProductionInfo; }
        }
        public TerminalInfo SymbolAsTerminal {
            get { return m_symbol as TerminalInfo; }
        }
        public bool IsOptional {
            get { return m_isOptional; }
        }
        public int Id {
            get { return m_id; }
        }
        public string Name {
            get {
                if (m_name == null) {
                    if (IsNonTerminal) {
                        var production = (ProductionInfo)m_symbol;
                        m_name = s_symbolNames.GetValueOrDefault(
                            new Tuple<string, int, int>(Production.Name, Expansion.Id, Id)) ??
                            production.Name;
                    } else {
                        var terminal = (TerminalInfo)m_symbol;
                        m_name = terminal.Name;
                    }
                }
                return m_name;    
            }
        }

        public override string ToString() {
            var result = m_symbol.ToString();
            if (IsTerminal && SymbolAsTerminal.Symbol != null)
                result = "'" + result + "'";
            if (IsOptional)
                result += "?";
            if (IsTerminal)
                result = result.ToUpper();
            return result;
        }
    }

    public class Program {
        private static string ExpansionAttr(ExpansionInfo expansion) {
            return new TextJoin(", ", prefix: "[Expansion(", suffix: ")]") {
                from o in expansion.Symbols()
                let nt = o.SymbolAsNonTerminal
                select o.IsTerminal ? 
                    (o.IsOptional ? "(Opt)" : null) + "Tk." + o.Name.ToUpperFirst() :
                    "typeof(" + o.SymbolAsNonTerminal.Name.ToUpperFirst() + ")"
            }.ReadToEnd();
        }
        private static string ToTerminators(GrammarInfo grammar, bool isOptional = false, bool isGrammar = false) {
            return new TextSingleSpaced() {
                from o in grammar.Terminals()
                where !isOptional || o.IsOptional
                orderby o.IsRegex, o.Name
                group o by o.Type into g
                select new object[] { " ", "        // " + g.Key,
                    from o in g
                    let opt = isOptional ? ", Opt" :  null
                    let eq = isOptional ? " = Token." + o.Name.ToUpperFirst() : null
                    let puctuator = o.IsPunctuator ? "[Terminal(" + o.Punctuator + ")" + opt + "]" : null
                    let dfa = o.IsRegex ? "[Terminal(DfaRegex." + o.Name.ToUpperFirst() + ")" + opt + "]" : null
                    let state = !isGrammar && o.IsKeyword ? "[Terminal(State = State.PpDirective)]" : null
                    select "        " + (!isOptional ? state + dfa + puctuator : null) + o.Name.ToUpperFirst() + eq + ","
                }
            }.ReadToEnd();
        }
        private static string ToGrammar(GrammarInfo grammar, string name) {
            var isGrammar = name == "Grammar";

            var code =
@"using System;
using System.Collections;
using System.Collections.Generic;
using King.Language.Grammar;
using OptAttribute = King.Language.Grammar.OptionalAttribute;

namespace King.Language.CSharp.{0} {{
    using Tk = Token;
    using Opt = OptionalToken;

    [GrammarAttribute(typeof(Token), typeof(OptionalToken))]
    public abstract class CSharpGrammar {{ }}

{1}
}}";
            var tokens =
@"    [Terminals]    
    public enum Token {{
{0}
    }}";

            var optionalTokens =
@"    [Terminals]
    public enum OptionalToken {{ 
{0}
    }}";

            var enumerable =@"

        public IEnumerator<{0}> GetEnumerator() {{ throw new NotImplementedException(); }}
        IEnumerator IEnumerable.GetEnumerator() {{ throw new NotImplementedException(); }}";

            var production =
@"    [Production{0}]
    public sealed class {1} : CSharpGrammar{2} {{
{3}{4}
    }}";

            var expansion =
@"        /// <summary>{0}</summary>
        {1}
        public static {2} Expansion{3}({4}) {{ throw new NotImplementedException(); }}";

            var symbol = @"            {0}{1} {2}";

            var stateEnum = @"    public enum State {
        PpDirective
    }";

            var result = code.Substitute(
                name, new TextDoubleSpaced() {
                    tokens.Substitute(ToTerminators(grammar, isGrammar: isGrammar)),
                    optionalTokens.Substitute(ToTerminators(grammar, isOptional: true, isGrammar: isGrammar)),
                    !isGrammar ? stateEnum : null,

                    new TextSingleSpaced() {
                        from p in grammar.Productions()
                        orderby p.Name
                        let productionName = p.Name.ToUpperFirst()
                        select production.Substitute(
                            productionName == "CompilationUnit" ? "(IsStart = true)" : null,
                            productionName, 
                            p.IsEnumeration ? ", IEnumerable<" + p.EnumerationOf.Name.ToUpperFirst() + ">" : null,
                            p.IsEnumeration ? enumerable.Substitute(p.EnumerationOf.Name.ToUpperFirst()) : null,
                            new TextSingleSpaced() {
                                from e in p.Expansions()
                                orderby e.Id
                                select expansion.Substitute(
                                    new TextJoin() { e.Symbols() }.ReadToEnd(),
                                    ExpansionAttr(e),
                                    productionName, 
                                    e.Id,
                                    new TextJoin("," + Environment.NewLine, prefix: Environment.NewLine) {
                                        from s in e.Symbols()
                                        where s.IsNonTerminal || (s.IsOptional && (s.Name != "whitespace" && s.Name != "SemiColon"))
                                        let nonTerminal = s.Symbol as ProductionInfo
                                        let terminal = s.Symbol as TerminalInfo
                                        select new TextSingleSpaced() {
                                            s.IsNonTerminal ?
                                                symbol.Substitute(
                                                    s.IsOptional ? "[Opt]" : null,
                                                    nonTerminal.IsEnumeration ? 
                                                        "IEnumerable<" + nonTerminal.EnumerationOf.Name.ToUpperFirst() + ">" : 
                                                        nonTerminal.Name.ToUpperFirst(),
                                                    s.Name) :
                                                symbol.Substitute(null,
                                                    "bool", 
                                                    "is" + s.Name.ToUpperFirst())
                                        }
                                    }.ReadToEnd()
                                )
                            }.ReadToEnd())
                    }.ReadToEnd()
                }.ReadToEnd()
            );

            return result;
        }

        private static HashSet<string> UnicodeGroups = new string[] {
            "Nd", "Mn", "Mc", "Pc", "Cf", "Lu", "Ll", "Lt", "Lm", "Lo", "Ln", "Zs"
        }.ToHashSet();
        private static string GetLexicalTerminal(string text) {
            if (text.StartsWith("/") && UnicodeGroups.Contains(text.TrimStart('/')))
                return "UnicodeGroup." + text.TrimStart('/');
            if (text.StartsWith("'\\u"))
                return text;
            if (text == "'") return "'\\''";
            if (text == "\\") return "'\\\\'";
            if (text.Length == 1)
                return "'" + text + "'";
            return "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
        }
        private static object ToLexicalExpansion(IEnumerable<SymbolInfo> symbols) {
            return new TextJoin(", ") {
                from s in symbols
                select s.IsNonTerminal ?
                    s.Name.ToUpperFirst() :
                    GetLexicalTerminal(s.SymbolAsTerminal.Text)
            };
        }
        private static object ToLexicalExpansion(ExpansionInfo expansion) {
            if (expansion.Symbols().First().Name == "{{") {
                var not = expansion.Symbols().SkipWhile(o => o.Name != "not").Skip(1).TakeWhile(o => o.Name != "}}");

                return new TextJoin(", ") {
                    ToLexicalExpansion(expansion.Symbols().Skip(1).TakeWhile(o => o.Name != "not")),
                    new TextJoin(", ", 
                        prefix: "Except = {0}".Substitute(not.OnlyOne() ? null : "new object[] { "), 
                        suffix: "{0}".Substitute(not.OnlyOne() ? null : " }")) {

                        ToLexicalExpansion(not),
                    }
                };
            }

            return ToLexicalExpansion(expansion.Symbols());
        }
        private static string ToLexicalGrammar(IEnumerable<ProductionInfo> productions) {
            var code =
@"using System;
using King.Language.Grammar;

namespace King.Language.CSharp.Grammar.PreProcessor {{

    public enum DfaRegex {{ 
{0}
    }}
}}";
            var result = code.Substitute(
                new TextDoubleSpaced() {
                    from p in productions
                    orderby p.Name
                    let productionName = p.Name.ToUpperFirst()
                    select new TextIndent("        ") {
                        new TextSingleSpaced() {
                            from e in p.Expansions()
                            select new TextJoin("", prefix: "[RegexProduction(", suffix: ")]") {
                                ToLexicalExpansion(e)
                            },
                            productionName + ","
                        }
                    }
                }.ReadToEnd());

            return result;
        }
        private static void DumpGrammar(GrammarInfo grammar) {
            foreach (var p in grammar.Productions().OrderBy(o => o.Name)) {
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine(p.Name);
                Console.ForegroundColor = ConsoleColor.White;

                foreach (var e in p.Expansions()) {
                    Console.Write("    ");

                    foreach (var s in e.Symbols()) {
                        Console.ForegroundColor =
                            (s.IsOptional && s.IsTerminal) ? ConsoleColor.Magenta :
                            s.IsOptional ? ConsoleColor.Yellow :
                            s.IsTerminal ? s.SymbolAsTerminal.IsRegex ? ConsoleColor.DarkRed : ConsoleColor.Red :
                            ConsoleColor.White;

                        var value = s.ToString();
                        if (s.IsNonTerminal && s.IsOptional)
                            value = value.TrimEnd('?');
                        if (s.IsTerminal)
                            value = ((TerminalInfo)s.Symbol).Text;

                        Console.Write(value + " ");

                        Console.ForegroundColor = ConsoleColor.White;
                    }

                    Console.WriteLine();
                }
            }
            Console.WriteLine("====================================");
            foreach (var o in from o in grammar.Terminals()
                              orderby o.IsRegex, o.ToString()
                              select o) {
                if (!o.IsRegex)
                    Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(o.ToString());
                Console.ForegroundColor = ConsoleColor.White;
            }
            Console.WriteLine("====================================");
            Console.WriteLine("Productions: " + grammar.Productions().Count());
            Console.WriteLine("Expansions: " + grammar.Productions().SelectMany(o => o.Expansions()).Count());
            Console.WriteLine("Terminals: " + grammar.Terminals().Count());
        }
        public static IEnumerable<ProductionInfo> 
            EnumProductions(GrammarInfo grammar, params string[] names) {

            var start = names.Select(o => grammar.GetProduction(o));

            var stack = new Stack<ProductionInfo>();
            var table = new HashSet<ProductionInfo>();

            stack.PushRange(start);
            table.AddRange(start);

            while (stack.Count > 0) {
                var cur = stack.Pop();
                yield return cur;
                var next = 
                   (from e in cur.Expansions()
                    from s in e.Symbols()
                    where s.IsNonTerminal && !table.Contains(s.SymbolAsNonTerminal)
                    select s.SymbolAsNonTerminal).Distinct().ToArray();
                table.AddRange(next);
                stack.PushRange(next);
            }
        }
        private static string ToAntlr(GrammarInfo lexGrammar, IEnumerable<ProductionInfo> regexs, HashSet<string> lexRoots) {
            var code =
@"grammar test; 

options 
{{
language = CSharp3; 
output = AST;
}}

{0}
";
            var maxPunc = lexGrammar.Terminals().Where(o => o.IsPunctuator).Max(o => o.Name.Length);
            var maxKey = lexGrammar.Terminals().Where(o => o.IsKeyword).Max(o => o.Name.Length);

            var nl = Environment.NewLine;
            var result = code.Substitute(
                new TextDoubleSpaced() {
                    new TextSingleSpaced() {
                        from p in lexGrammar.Terminals()
                        orderby p.Name
                        where p.IsPunctuator
                        select p.Name + "".PadLeft(maxPunc - p.Name.Length) + " : \"" + p.Symbol + "\";"
                    },

                    new TextSingleSpaced() {
                        from p in lexGrammar.Terminals()
                        orderby p.Name
                        where p.IsKeyword
                        select p.Name.ToUpperFirst() + "".PadLeft(maxPunc - p.Name.Length) + " : \"" + p.Name + "\";"
                    },

                    from p in regexs
                    let isRoot = lexRoots.Contains(p.Name)
                    orderby isRoot descending, p.Name
                    let productionName = p.Name.ToUpperFirst()
                    select new TextSingleSpaced(prefix: (!isRoot ? "fragment " : null) + productionName) {
                        new TextIndent("    ") {
                            new TextJoin(nl + "| ", nl + ": ", ";") {
                                from e in p.Expansions()
                                select new TextJoin() {
                                    ToLexicalExpansion(e)
                                },
                            }
                        }
                    }
                }.ReadToEnd());

            return result;
        }
        
        public static void Main(string[] args) {
            var lexGrammar = new GrammarInfo("lexical.txt");
            //DumpGrammar(lexGrammar);

            var roots = new[] { 
                "booleanLiteral", 
                "decimalIntegerLiteral", 
                "hexadecimalIntegerLiteral",
                "realLiteral",
                "characterLiteral",
                "regularStringLiteral",
                "verbatimStringLiteral",
                "nullLiteral",
                
                "singleLineComment",
                "delimitedComment",

                "whitespace",
                "newLine",
                "identifier",

                "notNumberSign",
                "conditionalSymbol",
                "fileNameCharacters"
            };
            var regexProductions = EnumProductions(lexGrammar, roots).OrderBy(o => o.Name).ToArray();

            var productionsToSkip = regexProductions.Select(o => o.Name).ToHashSet();
            var preGrammar = new GrammarInfo("lexical.txt", productionsToSkip);

            DumpGrammar(preGrammar);
            //File.WriteAllText(@"..\..\LexicalGrammar.cs", ToGrammar(preGrammar, "Grammar.PreProcessor"));

            //File.WriteAllText(@"..\..\Lexical.cs", ToLexicalGrammar(regexProductions));
            File.WriteAllText(@"..\..\Grammar.fsy", ToAntlr(preGrammar, regexProductions, roots.ToCaseInsensitiveHashSet()));

            var grammar = new GrammarInfo("grammar.txt");
            DumpGrammar(grammar);
            //File.WriteAllText(@"..\..\Grammar.cs", ToGrammar(grammar, "Grammar"));

            //var g = King.Language.Grammar.GrammarInfo.GetGrammar(typeof(King.Language.CSharp.Grammar.CSharpGrammar));
            //File.WriteAllText(@"..\..\Grammar.fsy", g.ToFsYacc().ReadToEnd());
        }
    }
}